#include <jni.h>
#include <string>
#include <vector>
#include <android/log.h>
#include <iostream>
#include <cmath>


using namespace std;


/**
 *
 * 在android开发过程中，由于主线程要聚焦于UI交互，为了软件运行流畅必然要用到很多多线程技术。
 * 而在JNI机制中专门提供了一些避免线程冲突的函数。了解、学习并掌握如何避免线程冲突问题是一个程序猿的必修课。下面简单介绍一下JNI与多线程。
 * 在JNI机制中，每一个线程进入本地函数（即c语言层）都会分配一个JNIenv对象，即是每个函数中第一个参数*env。env其实是VM的一个代理，
 * 通过env可以请求VM实现某些功能。就像每个进程都有一个独立的VM一样，每个通过JNI进入c层的线程都会分配一个env代理。这个env中包含了线程信息，
 * 并且每个线程只有一个全局的、伴随线程周期的env。各个线程的env之间彼此独立，当且仅当线程第一次进入JNI时分配env对象，该对象一直存在直到线程死掉。
 * 之后每次线程再进入时使用原有的env对象。
  这个env对象类似web开发中session的概念，对每一个客户端建立一个会话并保存，会话中可存放专属用某客户端数据以实现线程冲突。
  JNI中只有某些平台支持将变量存入env中，对于解决线程冲突问题，JNI提供的思路是加锁。以下是JNI对线程加锁的代码：
//加锁
env->monitorEnter(syncObj);
//执行代码
//解锁
  env->monitorExit(syncObj);
 *
 *
 *
 */

#define TAG "Jni_Base"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

extern "C" JNIEXPORT jstring JNICALL
Java_com_james_jni_1base_JavaTest_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT void JNICALL
Java_com_james_jni_1base_JavaTest_initIds(JNIEnv *env, jclass jcls) {
    LOGD("----------------LOGD---initIds----------------------");
    LOGI("----------------LOGI---initIds----------------------");
}


/**
 * 返回一个bytearray
 */
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_james_jni_1base_JavaTest_returnByteArray(JNIEnv *env, jobject) {
    jbyteArray jby = env->NewByteArray(3);
    std::vector<unsigned char> unchar(3);//定义一个vector
    unchar[0] = 1;
    unchar[1] = 2;
    unchar[2] = 3;

    //将char 转成 char 指针
    unsigned char *_cptr = unchar.data();//data 函数返回char 指针
    //将_cptr 的内容填充到 jbytearray 中
    env->SetByteArrayRegion(jby, 0, 3, reinterpret_cast<jbyte *>(_cptr));//reinterpret_cast 不同类型指针转换
    for_each(unchar.begin(), unchar.end(), [](const char &c) { LOGI("unchar = %c ", c); });
    return jby;
}

#include <unwind.h>
/**
 *  操作java 传递过来的string 和 对象数组 返回 jstring
 */
extern "C" JNIEXPORT jstring JNICALL
Java_com_james_jni_1base_JavaTest_MainopteratString(JNIEnv *env, jobject instance, jstring js,
                                                    jobjectArray joa) {
    std::string s = env->GetStringUTFChars(js, NULL);
    LOGI("%s = ", s.data());
    s.append("::::::Android developer)");
    for (int i = 0; i < env->GetArrayLength(joa); i++) {
//        LOGE("joa%d = %s", i, env->GetObjectArrayElement(joa,  i)); 直接获取object错误， 获取java的string 必须转成 c的string（GetStringUTFChars）
        LOGE("joa%d = %s", i,
             env->GetStringUTFChars((jstring) env->GetObjectArrayElement(joa, i),
                                    JNI_FALSE));//GetObjectArrayElement 返回jobject 后 可以直接强转成jstring
    }
    return env->NewStringUTF(s.data());//将返回给java
}


#define THREADNUMBER 5

#include <pthread.h>

JavaVM *g_vm;
jobject g_obj;




/**
 *  操作java 传递过来的string 和 对象数组 返回 jstring
 */
extern "C" JNIEXPORT void JNICALL
Java_com_james_jni_1base_JavaTest_CCallJavaStatic(JNIEnv *env, jobject jcls) {
    jclass jclass1 = env->FindClass("com/james/jni_base/JavaTest");//通过 类名全路径 找到jclass
    //通过jclass 找到 staticMethodId
    jmethodID id = env->GetStaticMethodID(jclass1, "cDiao",
                                          "(Ljava/lang/String;)V");//第一个参数 jclass 第二个 函数名称 第三个函数签名
    if (id == 0) {
        LOGE("call method error");
        return;
    }
//    env->CallStaticVoidMethod(jclass1, id,
//                              "c call java statice");//最后一个参数是返回值（可变参数） 注意这样写错误 不能将字符串直接传递给 java 应该使用 NewStringUTF

    env->CallStaticVoidMethod(jclass1, id, env->NewStringUTF("c call java jingtai"));


}


/**
 *
 * 通过jobject 对象 获取jclass 再找到 methodid 再调用java 方法
 *
 */
extern "C" JNIEXPORT void JNICALL
Java_com_james_jni_1base_JavaTest_CDiaoJavaByOject(JNIEnv *env, jobject jcls) {
    jclass js = env->GetObjectClass(jcls);
    jmethodID id = env->GetMethodID(js, "cNewObjDJ", "()V");//第一个参数 jclass 第二个参数是方法名称 第三个是方法签名
    env->CallVoidMethod(jcls, id);
}



/**
 *
 * 通过jobject 对象 获取jclass 再找到 FieldID  再改变 java 中的变量的值
 *
 */
extern "C" JNIEXPORT void JNICALL
Java_com_james_jni_1base_JavaTest_changeValue(JNIEnv *env, jobject jcls) {
    jclass js = env->GetObjectClass(jcls);
    jfieldID jfieldId = env->GetFieldID(js, "number", "I");
    int number = env->GetIntField(jcls, jfieldId);
    LOGI("number = %d", number);
    int newNum = static_cast<int>(pow(number, 2));
    LOGD("newNum = %d", newNum);
    env->SetIntField(jcls, jfieldId, newNum);

    jfieldID jid = env->GetFieldID(js, "content", "Ljava/lang/String;");
    jstring content = (jstring) env->GetObjectField(jcls, jid);
    const char *cc = env->GetStringUTFChars(content, 0);
    env->ReleaseStringUTFChars(content, cc);
    if (jid == 0) {
        LOGE("jid is error");
        return;
    }
    char hello[] = "I am from C\n";
    jstring cont = env->NewStringUTF(hello);
    env->SetObjectField(jcls, jid, cont);
}

extern "C"
JNIEXPORT void dynamicTest(JNIEnv *env, jobject instance, jstring text) {
    std::string c = env->GetStringUTFChars(text, nullptr);
    c.append(" is ndk developer");
    LOGI("dynamicJavaTest = %s", c.data());
}


extern "C"
JNIEXPORT void doit(JNIEnv *env, jobject instance) {
    jclass jcls = env->GetObjectClass(instance);

    jmethodID id = env->GetStaticMethodID(jcls, "exceptionCallback", "()V");

//    if (id != NULL) {
//        env->CallStaticVoidMethod(jcls, id);
//    }
//
//    LOGE("In C : Java_com_study_jnilearn_JNIException_doit-->called!!!!");
//    jboolean  check = env->ExceptionCheck();
//    LOGE("check = %d",check);
//    if (check) {//检查到异常
//        env->ExceptionDescribe();
//        env->ExceptionClear();//清空
//        env->ThrowNew(env->FindClass("java/lang/Exception"), "JNI抛出的异常！");//使用jni 调用java 异常类
//    }

    id = env->GetStaticMethodID(jcls, "normalCallback", "()V");

    if (id != nullptr) {
        env->CallStaticVoidMethod(jcls, id);
    }
}


extern "C"
JNIEXPORT void dynamicJavaStringTest(JNIEnv *env, jobject instance) {
    LOGI("dynamicJavaStringTest");
}

//extern "C"
//JNIEXPORT void JNICALL
//Java_com_james_jni_1base_KotlinTest_kotlinGetString(JNIEnv *env, jobject instance) {
//    LOGI("c to kotlin");
//}

extern "C"
JNIEXPORT jstring kotlinGetString(JNIEnv *env, jobject instance) {
    string c = "C to Kotlin";
    return env->NewStringUTF(c.data());
}



/**
 *
 *  1.设置 全局的JNIENV 和 jobject
 *  2.attachMainthread
 *  3.调用java 中方法
 */

extern "C"
JNIEXPORT void setJNIEnv(JNIEnv *env, jobject object) {
    env->GetJavaVM(&g_vm);//通过env 创建全局的java vm ，在Android 每一个进程独占一个vm ，每个vm 都一个多个JNIEnv
    //g_obj = object; 不能直接赋值
    g_obj = env->NewGlobalRef(object);
}

extern "C"
void *pthread_fun(void *arg) {
    JNIEnv *env;
    jclass jcls;
    jmethodID id;

    if (g_vm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
        LOGE("java vm  attach thread error");
        goto error;
    }
    jcls = env->GetObjectClass(g_obj);// 通过全局的global object 找到 jclass

    if (jcls == nullptr) {
        LOGE("env get class error");
        goto error;
    }


    id = env->GetStaticMethodID(jcls, "fromJNI", "(I)V");

    if (id == nullptr) {
        LOGE("env get jmethodID error");
        goto error;
    }
    env->CallStaticVoidMethod(jcls, id, arg);
    error:
    if (g_vm->DetachCurrentThread() != JNI_OK) {
        LOGE("java vm  detach thread error");
    }
    pthread_exit(nullptr);
}



extern "C"
JNIEXPORT void mainThread(JNIEnv *env, jobject instance) {
    pthread_t pt[THREADNUMBER];//pthread_t实际是个 long int，只不过用pthread_t表示线程ID
    for (int i = 0; i < THREADNUMBER; i++) {
        pthread_create(&pt[i], nullptr, pthread_fun,
                       (void *) i);//传四个参数 第一个线程id 第二个线程参数 可传NULL 第三个传函数指针 第四个void 指针
    }
}


JavaVM *_vm;

/**
 * JNI 动态注册
 */
static const char *className = "com/james/jni_base/JavaTest";


static const char *kotlinClassName = "com/james/jni_base/KotlinTest";


//JNINativeMethod 是结构体  第一个是native 方法名称 第二个是方法签名  第三个是jni 中的方法 （void 指针） ，这里定义一个JNINativeMethod 结构体数组
static const JNINativeMethod method[] = {
        {"doit",                  "()V",                   (void *) doit},
        {"setJNIEnv",             "()V",                   (void *) setJNIEnv},
        {"mainThread",            "()V",                   (void *) mainThread},
        {"dynamicJavaTests",      "(Ljava/lang/String;)V", (void *) dynamicTest},
        {"dynamicJavaStringTest", "()V",                   (void *) dynamicJavaStringTest}};//JNINativeMethod 结构体的参数 是void 类型指针 所以必须强转成（int 类型指针）

static const JNINativeMethod kotlinMethod = {"kotlinGetString", "()Ljava/lang/String;",
                                             (jstring *) kotlinGetString};


/**
 * 每个进程独享一个javavm ，每个线程拥有一个JNIENV
 *
 *
 * 这里是重写 jni.h 中的 JNI_OnLoad 方法
 */
int JNI_OnLoad(JavaVM *vm, void *r) {

    _vm = vm;

    JNIEnv *env = 0;

    jint result = vm->GetEnv((void **) &env,
                             JNI_VERSION_1_6);//GetEnv 获取的是 void 类型的二级指针 所以强转成void **,返回一个jint  如果失败返回-1

    if (result != JNI_OK) {
        LOGE("get JNIENV error");
        return result;
    }

    //根据类名找到类，注意有native的类不能被混淆
    jclass jcls = env->FindClass(className);
    jclass kcls = env->FindClass(kotlinClassName);
    //这时候注册本地方法 第一个参数jclass 第二个 method 数组  第三个 数组长度
    jint registresult = env->RegisterNatives(jcls, method,
                                             sizeof(method) / sizeof(JNINativeMethod));
    jint kotlinClassRegist = env->RegisterNatives(kcls, &kotlinMethod,
                                                  sizeof(kotlinMethod) /
                                                  sizeof(JNINativeMethod));//必须写 / sizeof(JNINativeMethod) 否则会报错 找不到方法
    LOGI("registresult=%d", registresult);//0 表示注册成功
    LOGI("kotlinClassRegist=%d", kotlinClassRegist);//0 表示注册成功
    return JNI_VERSION_1_6;
}




